package com.fouram.agent.handle;

import com.fouram.agent.util.*;
import oshi.PlatformEnum;
import oshi.SystemInfo;

import java.io.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * ExecuteHandleUtils
 * 
 * @author fouram
 */
public class ExecuteHandleUtils {

    private static BufferedWriter counterLogWriter = null;

    private static long nextDayStartTimeMillis;

    private static String logFileName = null;

    private static long startTimemillis;

    private static ScheduledThreadPoolExecutor LOG_EXECUTOR;

    private static Map<String, String> EXECUTE_MAP;

    private static final int BUFFER_SIZE = 256 * 1024;

    private static final String ENCODING = "UTF-8";

    private static boolean inited = false;

    private ExecuteHandleUtils() {
        super();
    }

    private static String MAIN_PROCESS_ID = null;

    private static void initJavaMainProcessId(){
        if(MAIN_PROCESS_ID == null){
            SystemInfo sys = new SystemInfo();
            MAIN_PROCESS_ID = String.valueOf(sys.getOperatingSystem().getProcessId());
        }
    }

    /**
     * 初使化
     *
     * @author fouram
     * @time 2015-7-30上午10:47:58
     */
    public static synchronized void init() {
        if (inited) {
            return;
        }
        logFileName = ConfigUtils.getLogFileName();
        int interval = ConfigUtils.getLogInterval();
        if (AgentUtils.isBlank(logFileName)) {
            System.err.println("日志文件名为空");
            throw new RuntimeException("日志文件名为空");
        }
        setNextDateStartTimeMillis();
        initWriter();
        EXECUTE_MAP = new ConcurrentHashMap<>(8);
        startTimemillis = System.currentTimeMillis();
        LOG_EXECUTOR = new ScheduledThreadPoolExecutor(1, new NamedThreadFactory("pool-thread-agent-log", true));
        LOG_EXECUTOR.scheduleWithFixedDelay(()->{
            try {
                outputCounterLog();
            } catch (IOException e) {
                System.err.println(e);
            }
        }, interval, interval, TimeUnit.SECONDS);
        initJavaMainProcessId();
        inited = true;
    }

    /**
     * 输出句柄相关信息
     * @param className
     * @param methodName
     */
    public static void log(String className, String methodName) {
        String result = "NOT LINUX";
        if(PlatformEnum.LINUX.equals(SystemInfo.getCurrentPlatformEnum())){
            String commandStr = "lsof -n|awk '{print $2}'|sort|uniq -c|sort -nr|more |grep " + MAIN_PROCESS_ID;
            result = (String) ExecLinuxCmd.exec(commandStr);
            if(result != null){
                result = result.replace(MAIN_PROCESS_ID,"").replace(" ", "");
            }
        }
        logExecute(className, methodName, result);
    }

    /**
     * 输出方法执行记数日志
     * 
     * @author fouram
     */
    public synchronized static void outputCounterLog() throws IOException {
        Map<String, String> inner = EXECUTE_MAP;
        EXECUTE_MAP = new ConcurrentHashMap<>(8);
        long byteLength = removeJsonArrayEndBracket();
        if (0 == byteLength) {
            writeLog("[",true, startTimemillis);
        }

        Set<Map.Entry<String, String>> entrySet = inner.entrySet();
        Iterator<Map.Entry<String, String>> ite = entrySet.iterator();
        int length = entrySet.size();
        // 说明文件不只是[],json数组中已经有内容
        if (length > 0 && byteLength > 10) {
            writeLog(",", startTimemillis);
        }
        for (int index = 0; ite.hasNext(); index++) {
            Map.Entry<String, String> entry = ite.next();
            String className = entry.getKey();
            String handleResult = entry.getValue();
            String methodExecuteJson = getMethodExecuteJson(className, handleResult);
            writeLog(methodExecuteJson, startTimemillis);
            if (index < length - 1) {
                writeLog(",", true, startTimemillis);
            } else {
                writeLog("", true, startTimemillis);
            }
        }
        writeLog("]",startTimemillis);
        flushLogAndClose();
        startTimemillis = System.currentTimeMillis();
    }

    public static String getMethodExecuteJson(String className, String handleResult) {
        StringBuilder json = new StringBuilder("{");
        StringBuilderUtils.appendString(json, "class", className).append(",");
        StringBuilderUtils.appendString(json, "handle", handleResult);
        json.append("}");
        return json.toString();
    }

    private static void logExecute(String className, String methodName, String handleResult) {
        synchronized (EXECUTE_MAP) {
            EXECUTE_MAP.put(className + "." + methodName + "." + UUID.randomUUID(), handleResult);
        }
    }

    private static void writeLog(String logValue, long currTimeMillis) {
        writeLog(logValue, false, currTimeMillis);
    }

    private static void writeLog(String logValue, boolean newLine, long currTimeMillis) {
        ensureLogFileUpToDate(currTimeMillis);
        try {
            counterLogWriter.write(logValue);
            if (newLine) {
                counterLogWriter.newLine();
            }
        } catch (IOException e) {
            System.err.println(e);
        }

    }

    private static void flushLogAndClose() {
        try {
            counterLogWriter.flush();
        } catch (IOException e) {
            System.err.println(e);
        } finally {
            AgentUtils.closeQuietly(counterLogWriter);
            counterLogWriter = null;
        }
    }

    /**
     * 确保日志文件没过时,日志文件都会加上日期后缀,如果当前日志文件
     */
    private static void ensureLogFileUpToDate(long currTimeMillis) {
        if (currTimeMillis >= nextDayStartTimeMillis) {
            try {
                if (null != counterLogWriter) {
                    counterLogWriter.flush();
                }
            } catch (Exception e) {
                System.err.println(e);
            } finally {
                AgentUtils.closeQuietly(counterLogWriter);
            }
            initWriter();
            setNextDateStartTimeMillis();
        }
        if (null == counterLogWriter) {
            initWriter();
        }

    }

    private static void initWriter() {
        try {
            File logFile = getCounterLogFile(logFileName);
            counterLogWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(logFile, true), ENCODING),
                BUFFER_SIZE);
        } catch (IOException e) {
            System.err.println(e);
            throw new RuntimeException("无法初始化【" + logFileName + "】,建议您检查磁盘空间，或者手动删除该日志文件");
        }
    }

    private static File getCounterLogFile(String logFileName) throws IOException {
        String logFileNameWithDate = getCurrDateString();
        int lastIndexOfDot = logFileName.lastIndexOf('.');
        if (lastIndexOfDot > 0) {
            logFileName = logFileName.substring(0, lastIndexOfDot) + "." + logFileNameWithDate
                + logFileName.substring(lastIndexOfDot);
        } else {
            logFileName = logFileName + "." + logFileNameWithDate + ".log";
        }
        File logFile = getLogFile(logFileName);
        return logFile;
    }

    private static File getLogFile(String logFileName) throws IOException {
        File logFile = new File(logFileName);
        if (logFile.isDirectory()) {
            System.err.println("【" + logFileName + "】是文件夹");
            throw new RuntimeException("【" + logFileName + "】是文件夹");
        } else if (!logFile.exists()) {
            // dingjs modified in 20140513
            File dirFile = logFile.getParentFile();
            AgentUtils.forceMkdir(dirFile);
            boolean created = logFile.createNewFile();
            if (!created) {
                System.err.println("【" + logFileName + "】创建失败");
                throw new RuntimeException("【" + logFileName + "】创建失败");
            }
        }
        return logFile;
    }

    private static String getCurrDateString() {
        Date today = new Date();
        return new SimpleDateFormat("yyyy-MM-dd").format(today);
    }

    /**
     * 由开始日期转换为开始时间，一般在以时间为条件的查询中使用
     */
    private static Date date2StartTime(Date date) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        String dateStr = sdf.format(date);
        Date startTime = null;
        try {
            startTime = sdf.parse(dateStr + " 00:00:00");
        } catch (ParseException e) {
            System.err.println(e);
        }
        return startTime;
    }

    /**
     * 设置下一天的开始时间
     */
    private static void setNextDateStartTimeMillis() {
        Date currDate = new Date();
        Calendar cal = Calendar.getInstance();
        cal.setTime(currDate);
        cal.add(Calendar.DATE, 1);
        Date startTime = date2StartTime(cal.getTime());
        nextDayStartTimeMillis = startTime.getTime();
    }

    private static String foramteTimeMillis(long timeMillis) {
        Date date = new Date(timeMillis);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.format(date);
    }

    private static long removeJsonArrayEndBracket() throws IOException {
        File logFile = getCounterLogFile(logFileName);
        RandomAccessFile f = new RandomAccessFile(logFile.getAbsolutePath(), "rw");
        long length = f.length();
        byte b = -1;
        while (b != 93 && length > 0) {
            length -= 1;
            f.seek(length);
            b = f.readByte();
        }
        f.setLength(length);
        f.close();
        return length;
    }
}